/**
 * @filename HttpInclude.java
 */
package com.liupf.extend.freemarker;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @description 
 * @author shevliu
 * @email shevliu@gmail.com
 * Aug 2, 2012 9:32:02 PM
 */
public class HttpInclude {

	 private final static Log log = LogFactory.getLog(HttpInclude.class);
     
     public static String sessionIdKey = "JSESSIONID";
     
 private HttpServletRequest request;
 private HttpServletResponse response;
 
 public HttpInclude(HttpServletRequest request, HttpServletResponse response) {
     this.request = request;
     this.response = response;
 }

     public String include(String includePath) {
             StringWriter sw = new StringWriter(8192);
             include(includePath,sw);
             return sw.toString();
 }

     public void include(String includePath,Writer writer) {
     try {
         if(isRemoteHttpRequest(includePath)) {
             getRemoteContent(includePath,writer);
         }else {
        	 log.debug("request.getServerName()" + request.getServerName());
        	 log.debug("request.getServerName()" + request.getContextPath());
        	 log.debug("request.getServerName()" + request.getServletPath());
        	 String path = "http://localhost:" + request.getServerPort()  ; 
        	 if(!"".equals(request.getContextPath())){
        		 path = path + "/" + request.getContextPath();
        	 }
        	 path+=includePath ;
        	 log.debug("path is :" + path);
        	 getRemoteContent(path,writer);
//             getLocalContent(includePath,writer);
         }
     }  catch (IOException e) {
         throw new RuntimeException("include error,path:"+includePath+" cause:"+e,e);
     }
 }
     
 private static boolean isRemoteHttpRequest(String includePath) {
     return  includePath != null && (
                             includePath.toLowerCase().startsWith("http://") ||
                             includePath.toLowerCase().startsWith("https://")
                     );
 }

 private void getLocalContent(String includePath,Writer writer) throws ServletException, IOException {
     ByteArrayOutputStream outputStream = new ByteArrayOutputStream(8192);
     
     CustomOutputHttpServletResponseWrapper customResponse = new CustomOutputHttpServletResponseWrapper(response,writer,outputStream);
     request.getRequestDispatcher(includePath).include(request, customResponse);
     
     customResponse.flushBuffer();
     if(customResponse.useOutputStream) {
             writer.write(outputStream.toString(response.getCharacterEncoding())); //TODO: response.getCharacterEncoding()有可能为null
     }
     writer.flush();
 }
 
 //TODO handle cookies and http query parameters encoding
 //TODO set inheritParams from request
 private void getRemoteContent(String urlString,Writer writer) throws MalformedURLException, IOException {
     URL url = new URL(getWithSessionIdUrl(urlString));
             URLConnection conn = url.openConnection();
     setConnectionHeaders(urlString, conn);
     InputStream input = conn.getInputStream();
     try {
             Reader reader = new InputStreamReader(input,Utils.getContentEncoding(conn,response));
             Utils.copy(reader,writer);
     }finally {
             if(input != null) input.close();
     }
     writer.flush();
 }

     private void setConnectionHeaders(String urlString, URLConnection conn) {
             conn.setReadTimeout(6000);
     conn.setConnectTimeout(6000);
     String cookie = getCookieString();
             conn.setRequestProperty("Cookie", cookie);
             //TODO: 用于支持 httpinclude_header.properties
//           conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3");
//           conn.setRequestProperty("Host", url.getHost());
             if(log.isDebugEnabled()) {
                     log.debug("request properties:"+conn.getRequestProperties()+" for url:"+urlString);
             }
     }
     
     //TODO add session id with url
     private String getWithSessionIdUrl(String url) {
             return url;
     }

 private static final String SET_COOKIE_SEPARATOR="; ";
     private String getCookieString() {
             StringBuffer sb = new StringBuffer(64);
             Cookie[] cookies = request.getCookies();
             if(cookies != null ) {
                     for(Cookie c : cookies) {
                             if(!sessionIdKey.equals(c.getName())) {
                                     sb.append(c.getName()).append("=").append(c.getValue()).append(SET_COOKIE_SEPARATOR);
                             }
                     }
             }
             
             String sessionId = Utils.getSessionId(request);
             if(sessionId != null) {
                     sb.append(sessionIdKey).append("=").append(sessionId).append(SET_COOKIE_SEPARATOR);
             }
             return sb.toString();
     }
     
     public static class CustomOutputHttpServletResponseWrapper extends HttpServletResponseWrapper {
     public boolean useWriter = false;
     public boolean useOutputStream = false;
//     
     private PrintWriter printWriter;
     private ServletOutputStream servletOutputStream;
     
     public CustomOutputHttpServletResponseWrapper(HttpServletResponse response,final Writer customWriter,final OutputStream customOutputStream) {
         super(response);
         this.printWriter = new PrintWriter(customWriter);
         this.servletOutputStream = new ServletOutputStream() {
             @Override
             public void write(int b) throws IOException {
                 customOutputStream.write(b);
             }
             @Override
             public void write(byte[] b) throws IOException {
                 customOutputStream.write(b);
             }
             @Override
             public void write(byte[] b, int off, int len)throws IOException {
                 customOutputStream.write(b, off, len);
             }
         };
     }
     @Override
     public PrintWriter getWriter() throws IOException {
         if(useOutputStream) throw new IllegalStateException("getOutputStream() has already been called for this response");
         useWriter = true;
         return printWriter;
     }
     @Override
     public ServletOutputStream getOutputStream() throws IOException {
         if(useWriter) throw new IllegalStateException("getWriter() has already been called for this response");
         useOutputStream = true;
         return servletOutputStream;
     }
     
     @Override
     public void flushBuffer() throws IOException {
         if(useWriter) printWriter.flush();
         if(useOutputStream) servletOutputStream.flush();
     }
     
     }

 static class Utils { 
     static String getContentEncoding(URLConnection conn,HttpServletResponse response) {
             String contentEncoding = conn.getContentEncoding();
         if(conn.getContentEncoding() == null) {
             contentEncoding = parseContentTypeForCharset(conn.getContentType());
             if(contentEncoding == null) {
                     contentEncoding =  response.getCharacterEncoding();
             }
         } else {
             contentEncoding = conn.getContentEncoding();
         }
             return contentEncoding;
     }
     static Pattern p = Pattern.compile("(charset=)(.*)",Pattern.CASE_INSENSITIVE);
     private static String parseContentTypeForCharset(String contentType) {
             if(contentType == null) return null;
             Matcher m = p.matcher(contentType);
             if(m.find()) {
                     return m.group(2).trim();
             }
                     return null;
             }

             private static void copy(Reader in, Writer out) throws IOException {
         char[] buff = new char[8192];
         while(in.read(buff) >= 0) {
             out.write(buff);
         }
     }
     
     private static String getSessionId(HttpServletRequest request) {
             HttpSession session = request.getSession(false);
             if(session == null) {
                     return null;
             }
             return session.getId();
     }
 }
}
